module scene.camera;

import utils.vector2;
import utils.vector3;
import utils.matrix;
import utils.mover3;
import scene.ray;
import std.stdio;
import std.math;

class Camera {
    Mover3f mCamMover;
    float mF;
    Vector3f mImgPlaneX, mImgPlaneDist;

    public void delegate() onChangeView;

    this(Vector3f pos, Vector3f dir, Vector3f up, float f) {
        mCamMover = Mover3f(pos,dir.normal(),up.normal());
        mF = PI/4;
        precalc();
    }

    ///default camera for coordinate system [-0.5,0.5]^2
    this() {
        this(Vector3f(0,0,0), Vector3f(0,0,-1), Vector3f(0,1,0), 0.5f);
    }

    public void set(Vector3f pos, Vector3f dir, Vector3f up) {
        mCamMover.pos = pos;
        mCamMover.dir = dir.normal();
        mCamMover.up = up.normal();
        precalc();
    }
    public void set(Vector3f pos, Vector3f dir) {
        mCamMover.pos = pos;
        mCamMover.updateDir(dir);
        precalc();
    }

    private void precalc() {
        mImgPlaneX = mCamMover.side();
        mImgPlaneDist = mCamMover.dir * tan(mF);
        if (onChangeView)
            onChangeView();
    }

    ///generate a ray to shoot into the scene
    public Ray generateRay(Vector2f pos) {
        Ray res;
        res = Ray(mCamMover.pos, (mImgPlaneDist + mImgPlaneX*pos.x +
            mCamMover.up*pos.y).normal(), float.max);

        res.rdif_dPx = Vector3f(0,0,0);
        res.rdif_dPy = Vector3f(0,0,0);
        float ddotd = res.dir*res.dir;
        float ddotdp15inv = 1.0f/pow(ddotd,1.5f);
        res.rdif_dDx = (ddotd*mImgPlaneX-(res.dir*mImgPlaneX)*res.dir)*ddotdp15inv;
        res.rdif_dDy = (ddotd*mCamMover.up-(res.dir*mCamMover.up)*res.dir)*ddotdp15inv;

        return res;
    }

    public void lookAt(Vector3f point, Vector3f up) {
        mCamMover.lookAt(point,up);
        precalc();
    }

    public void lookAtFrom(Vector3f pos, Vector3f point) {
        mCamMover.lookAtFrom(pos,point);
        precalc();
    }

    public void lookAt(Vector3f point) {
        mCamMover.lookAt(point);
        precalc();
    }

    public void moveRelative(Vector3f moveRel) {
        mCamMover.moveRelative(moveRel);
        precalc();
    }

    public void rotateRelative(Vector3f rel) {
        mCamMover.rotateRelative(rel);
        precalc();
    }

    public void moveAbsolute(Vector3f moveVec) {
        mCamMover.moveAbsolute(moveVec);
        writefln("%s",mCamMover.pos);
        precalc();
    }

    public Vector3f position() {
        return mCamMover.pos;
    }

    public Vector3f direction() {
        return mCamMover.dir;
    }

    public Vector3f up() {
        return mCamMover.up;
    }
}
